// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

package common

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/changelog"
	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/cmd/template"
	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/repo"
	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/typespec"
	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/utils"
	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/version"
	"github.com/Masterminds/semver"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

type GenerateContext struct {
	SDKPath           string
	SDKRepo           *repo.SDKRepository
	SpecPath          string
	SpecCommitHash    string
	SpecReadmeFile    string
	SpecReadmeGoFile  string
	SpecRepoURL       string
	UpdateSpecVersion bool

	// typespec
	TypeSpecConfig *typespec.TypeSpecConfig
}

type GenerateResult struct {
	Version             string
	RPName              string
	PackageName         string
	PackageAbsPath      string
	Changelog           changelog.Changelog
	ChangelogMD         string
	PullRequestLabels   string
	PackageRelativePath string
	ModuleRelativePath  string
	GenerationType      string
}

type GenerateParam struct {
	RPName               string
	NamespaceName        string
	NamespaceConfig      string
	SpecificPackageTitle string
	SpecRPName           string
	ReleaseDate          string
	SkipGenerateExample  bool
	RemoveTagSet         bool
	ForceStableVersion   bool
	TypeSpecEmitOption   string
	TspClientOptions     []string
	ApiVersion           string
	SdkReleaseType       string
	SkipUpdateDep        bool
}

type Generator interface {
	PreGenerate(generateParam *GenerateParam) error
	Generate(generateParam *GenerateParam) error
	GenChangeLog(generateParam *GenerateParam) error
	AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error)
}

// Common steps for onboard and normal service
type SwaggerCommonGenerator struct {
	Version          *semver.Version
	PackagePath      string
	IsCurrentPreview bool
	ChangelogResult  changelog.ChangelogResult
	*GenerateContext
}

type SwaggerOnBoardGenerator struct {
	*SwaggerCommonGenerator
}

type SwaggerUpdateGenerator struct {
	*SwaggerCommonGenerator
}

type TypeSpecCommonGenerator struct {
	PackageRelativePath string
	ModuleRelativePath  string
	PackagePath         string
	ModulePath          string
	Version             *semver.Version
	IsSubPackage        bool
	IsCurrentPreview    bool
	ChangelogResult     changelog.ChangelogResult
	*GenerateContext
}

type TypeSpecOnBoardGenerator struct {
	*TypeSpecCommonGenerator
}

type TypeSpecUpdateGenerator struct {
	*TypeSpecCommonGenerator
}

func (ctx *GenerateContext) GenerateFromTypeSpec(tspconfigPath string, commonGenerateParam *GenerateParam) (*GenerateResult, error) {
	// typespec
	// Generated by tsp only when tspconfig.yaml exists and has typespec-go option
	tsc, err := typespec.ParseTypeSpecConfig(tspconfigPath)
	if err != nil {
		return nil, fmt.Errorf("failed to parse %s: %+v\nInvalid tspconfig.yaml provided and refer to the sample file to fix the content. management-plane: https://aka.ms/azsdk/tspconfig-sample-mpg, data-plane: https://aka.ms/azsdk/tspconfig-sample-dpg", tspconfigPath, err)
	}

	if ok, _ := tsc.ExistEmitOption(); !ok {
		log.Printf("`@azure-tools/typespec-go` option not found in %s, it is required, please refer to the sample file to configure it. management-plane: https://aka.ms/azsdk/tspconfig-sample-mpg, data-plane: https://aka.ms/azsdk/tspconfig-sample-dpg", tspconfigPath)
		return nil, nil
	}
	ctx.TypeSpecConfig = tsc
	// reuse the same param for all typespec projects
	generateParam := commonGenerateParam
	namespaceResult, err := ctx.GenerateForSingleTypeSpec(generateParam)
	if err != nil {
		return nil, err
	}
	return namespaceResult, nil
}

func (ctx *GenerateContext) GenerateFromSwagger(rpMap map[string][]PackageInfo, commonGenerateParam *GenerateParam) ([]*GenerateResult, []error) {
	// autorest
	errors := make([]error, 0)
	results := make([]*GenerateResult, 0)
	for rpName, packageInfos := range rpMap {
		for _, packageInfo := range packageInfos {
			log.Printf("Start to process rp: %s, namespace: %s", rpName, packageInfo.Name)
			singleResult, err := ctx.GenerateForSingleRPNamespace(&GenerateParam{
				RPName:               rpName,
				NamespaceName:        packageInfo.Name,
				SpecRPName:           packageInfo.SpecName,
				NamespaceConfig:      packageInfo.Config,
				ReleaseDate:          commonGenerateParam.ReleaseDate,
				RemoveTagSet:         commonGenerateParam.RemoveTagSet,
				SkipGenerateExample:  commonGenerateParam.SkipGenerateExample,
				SpecificPackageTitle: commonGenerateParam.SpecificPackageTitle,
				ForceStableVersion:   commonGenerateParam.ForceStableVersion,
			})
			if err != nil {
				errors = append(errors, fmt.Errorf("failed to generate for rp: %s, namespace: %s: %+v", rpName, packageInfo.Name, err))
				continue
			}
			results = append(results, singleResult)
			log.Printf("Finish processing rp: %s, namespace: %s", rpName, packageInfo.Name)
		}
	}
	return results, errors
}

func (ctx *GenerateContext) GenerateForSingleRPNamespace(generateParam *GenerateParam) (*GenerateResult, error) {
	packagePath := filepath.Join(ctx.SDKPath, "sdk", "resourcemanager", generateParam.RPName, generateParam.NamespaceName)

	version, err := semver.NewVersion("0.1.0")
	if err != nil {
		return nil, err
	}

	var generator Generator
	commonGenerator := &SwaggerCommonGenerator{GenerateContext: ctx, PackagePath: packagePath, Version: version}

	// check if the package is onboard or update, to init different generator
	status, err := changelog.DetermineModuleStatus(packagePath, *ctx.SDKRepo)
	if err != nil {
		return nil, err
	}
	switch status {
	case utils.PackageStatusNew:
		generator = &SwaggerOnBoardGenerator{SwaggerCommonGenerator: commonGenerator}
	default:
		generator = &SwaggerUpdateGenerator{SwaggerCommonGenerator: commonGenerator}
	}

	if err = generator.PreGenerate(generateParam); err != nil {
		return nil, err
	}

	err = generator.Generate(generateParam)
	if err != nil {
		return nil, err
	}

	err = generator.GenChangeLog(generateParam)
	if err != nil {
		return nil, err
	}

	return generator.AfterGenerate(generateParam)
}

func (t *SwaggerCommonGenerator) PreGenerate(generateParam *GenerateParam) error {
	packagePath := t.PackagePath
	// same step for onboard and update
	if t.SpecCommitHash == "" {
		log.Printf("Change swagger config in `autorest.md` according to local path...")
		autorestMdPath := filepath.Join(packagePath, "autorest.md")
		if err := ChangeConfigWithLocalPath(autorestMdPath, t.SpecReadmeFile, t.SpecReadmeGoFile); err != nil {
			return err
		}
	} else {
		log.Printf("Change swagger config in `autorest.md` according to repo URL and commit ID...")
		autorestMdPath := filepath.Join(packagePath, "autorest.md")
		if err := ChangeConfigWithCommitID(autorestMdPath, t.SpecRepoURL, t.SpecCommitHash, generateParam.SpecRPName); err != nil {
			return err
		}
	}

	// add tag set
	if !generateParam.RemoveTagSet && generateParam.NamespaceConfig != "" {
		log.Printf("Add tag in `autorest.md`...")
		autorestMdPath := filepath.Join(packagePath, "autorest.md")
		if err := AddTagSet(autorestMdPath, generateParam.NamespaceConfig); err != nil {
			return err
		}
	}

	// remove tag set
	if generateParam.RemoveTagSet {
		log.Printf("Remove tag set for swagger config in `autorest.md`...")
		autorestMdPath := filepath.Join(packagePath, "autorest.md")
		if err := RemoveTagSet(autorestMdPath); err != nil {
			return err
		}
	}

	return nil
}

func (t *SwaggerCommonGenerator) Generate(generateParam *GenerateParam) error {
	packagePath := t.PackagePath
	log.Printf("Start to run `go generate` to regenerate the code...")
	if err := ExecuteGoGenerate(packagePath); err != nil {
		return err
	}
	return nil
}

func (t *SwaggerCommonGenerator) GenChangeLog(generateParam *GenerateParam) error {
	log.Printf("Start to generate changelog for package...")
	var err error
	t.ChangelogResult, err = changelog.GenerateChangelog(t.PackagePath, *t.SDKRepo, t.IsCurrentPreview)
	if err != nil {
		return err
	}
	return nil
}

func (t *SwaggerCommonGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	packagePath := t.PackagePath

	// Example generation should be the last step because the package import relay on the new calculated version
	if !generateParam.SkipGenerateExample {
		log.Printf("Start to generate examples...")
		var flags []string
		alwaysSetBodyParamRequiredFlag, err := GetAlwaysSetBodyParamRequiredFlag(filepath.Join(packagePath, "build.go"))
		if err != nil {
			return nil, err
		}
		if len(alwaysSetBodyParamRequiredFlag) > 0 {
			flags = append(flags, alwaysSetBodyParamRequiredFlag)
		}
		clientFactoryParamsFlag, err := GetFactoryGatherAllParamsFlag(filepath.Join(packagePath, "build.go"))
		if err != nil {
			return nil, err
		}
		if len(clientFactoryParamsFlag) > 0 {
			flags = append(flags, clientFactoryParamsFlag)
		}
		log.Println(flags)
		if err := ExecuteExampleGenerate(packagePath, filepath.Join("resourcemanager", generateParam.RPName, generateParam.NamespaceName), flags); err != nil {
			return nil, err
		}
	}
	log.Printf("Update README.md ClientFactory...")
	err := UpdateReadmeClientFactory(t.PackagePath)
	if err != nil {
		// only log error, avoid breaking the process
		log.Printf("Update README.md ClientFactory failed! err: %v", err)
	}
	return nil, nil
}

func (t *SwaggerOnBoardGenerator) PreGenerate(generateParam *GenerateParam) error {
	var err error
	version := t.Version
	packagePath := t.PackagePath
	log.Printf("Package '%s' changelog not exist, do onboard process", packagePath)

	if generateParam.SpecificPackageTitle == "" {
		generateParam.SpecificPackageTitle = strings.Title(generateParam.RPName)
	}

	// onboard is always preview
	t.IsCurrentPreview = true

	log.Printf("Start to use template to generate new rp folder and basic package files...")
	if err = template.GeneratePackageByTemplate(generateParam.RPName, generateParam.NamespaceName, template.Flags{
		SDKRoot:        t.SDKPath,
		TemplatePath:   "eng/tools/generator/template/rpName/packageName",
		PackageTitle:   generateParam.SpecificPackageTitle,
		Commit:         t.SpecCommitHash,
		PackageConfig:  generateParam.NamespaceConfig,
		PackageVersion: version.String(),
		ReleaseDate:    generateParam.ReleaseDate,
	}); err != nil {
		return err
	}
	if err = t.SwaggerCommonGenerator.PreGenerate(generateParam); err != nil {
		return err
	}
	return nil
}

func (t *SwaggerOnBoardGenerator) GenChangeLog(generateParam *GenerateParam) error {
	if err := t.SwaggerCommonGenerator.GenChangeLog(generateParam); err != nil {
		return err
	}
	if err := changelog.CreateNewChangelog(t.PackagePath, *t.SDKRepo, "0.1.0", generateParam.ReleaseDate); err != nil {
		return err
	}
	return nil
}

func (t *SwaggerOnBoardGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	var err error
	var prl utils.PullRequestLabel
	version := t.Version
	packagePath := t.PackagePath
	log.Printf("Replace {{NewClientName}} placeholder in the README.md ")
	if err = ReplaceNewClientNamePlaceholder(packagePath, *t.ChangelogResult.NewExports); err != nil {
		return nil, err
	}

	if _, err := t.SwaggerCommonGenerator.AfterGenerate(generateParam); err != nil {
		return nil, err
	}

	// issue: https://github.com/Azure/azure-sdk-for-go/issues/23877
	prl = utils.FirstBetaLabel
	packageRelativePath := fmt.Sprintf("sdk/resourcemanager/%s/%s", generateParam.RPName, generateParam.NamespaceName)
	return &GenerateResult{
		Version:             version.String(),
		RPName:              generateParam.RPName,
		PackageName:         generateParam.NamespaceName,
		PackageAbsPath:      packagePath,
		Changelog:           *t.ChangelogResult.ChangelogData,
		ChangelogMD:         t.ChangelogResult.ChangelogData.ToCompactMarkdown() + "\n" + t.ChangelogResult.ChangelogData.GetChangeSummary(),
		PullRequestLabels:   string(prl),
		PackageRelativePath: packageRelativePath,
		GenerationType:      "SwaggerOnBoard",
	}, nil
}

func (t *SwaggerUpdateGenerator) PreGenerate(generateParam *GenerateParam) error {
	packagePath := t.PackagePath
	var err error
	log.Printf("Package '%s' existed, do update process", packagePath)

	log.Printf("Remove all the generated files ...")
	if err = CleanSDKGeneratedFiles(packagePath); err != nil {
		return err
	}

	override := new(bool)
	if generateParam.ForceStableVersion {
		*override = true
	}
	if t.IsCurrentPreview, err = version.IsCurrentPreviewVersion(packagePath, *t.SDKRepo, override); err != nil {
		return err
	}

	if err = t.SwaggerCommonGenerator.PreGenerate(generateParam); err != nil {
		return err
	}
	return nil
}

func (t *SwaggerUpdateGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	isCurrentPreview := t.IsCurrentPreview
	packagePath := t.PackagePath

	log.Printf("Calculate new version...")
	newVersion, prl, err := version.CalculateNewVersion(t.ChangelogResult.ChangelogData, t.ChangelogResult.PreviousVersion, isCurrentPreview)
	if err != nil {
		return nil, err
	}

	log.Printf("Add changelog to file...")
	changelogMd, err := changelog.AddChangelogToFileWithReplacement(t.ChangelogResult.ChangelogData, newVersion, packagePath, generateParam.ReleaseDate)
	if err != nil {
		return nil, err
	}

	log.Printf("Update all version files...")
	if err = version.UpdateAllVersionFiles(packagePath, newVersion, *t.SDKRepo); err != nil {
		return nil, err
	}

	log.Printf("Replace README.md NewClient name...")
	if err = ReplaceReadmeNewClientName(packagePath, *t.ChangelogResult.NewExports); err != nil {
		return nil, err
	}

	if _, err := t.SwaggerCommonGenerator.AfterGenerate(generateParam); err != nil {
		return nil, err
	}

	packageRelativePath := fmt.Sprintf("sdk/resourcemanager/%s/%s", generateParam.RPName, generateParam.NamespaceName)
	return &GenerateResult{
		Version:             newVersion.String(),
		RPName:              generateParam.RPName,
		PackageName:         generateParam.NamespaceName,
		PackageAbsPath:      packagePath,
		Changelog:           *t.ChangelogResult.ChangelogData,
		ChangelogMD:         changelogMd + "\n" + t.ChangelogResult.ChangelogData.GetChangeSummary(),
		PullRequestLabels:   string(prl),
		PackageRelativePath: packageRelativePath,
		GenerationType:      "SwaggerUpdate",
	}, nil
}

func (ctx *GenerateContext) GenerateForSingleTypeSpec(generateParam *GenerateParam) (*GenerateResult, error) {
	isSubPackage := false

	packageRelativePath := ctx.TypeSpecConfig.GetPackageRelativePath()
	if packageRelativePath == "" {
		return nil, fmt.Errorf("package module relative path not found in %s", ctx.TypeSpecConfig.Path)
	}

	moduleRelativePath := ctx.TypeSpecConfig.GetModuleRelativePath()
	// if module relative path is not provided, find it from the sdk path by go.mod
	if moduleRelativePath == "" {
		isSubPackage = true
		val, err := FindModuleDirByGoMod(filepath.Join(ctx.SDKPath, packageRelativePath))
		if err != nil {
			return nil, err
		}
		if moduleRelativePath, err = filepath.Rel(ctx.SDKPath, val); err != nil {
			return nil, err
		}
		moduleRelativePath = filepath.ToSlash(moduleRelativePath)
	}

	if !strings.HasPrefix(packageRelativePath, moduleRelativePath) {
		return nil, fmt.Errorf("module relative path '%s' is not a prefix of package relative path '%s', please check your tspconfig.yaml file", moduleRelativePath, packageRelativePath)
	}

	if packageRelativePath != moduleRelativePath {
		isSubPackage = true
	}

	// if rp name and namespace name are not provided, extract them from the module path
	if len(generateParam.RPName) == 0 && len(generateParam.NamespaceName) == 0 {
		rpAndNamespaceName, err := ctx.TypeSpecConfig.GetRpAndPackageNameByModule(moduleRelativePath)
		if err != nil {
			return nil, err
		}
		generateParam.RPName = rpAndNamespaceName[0]
		generateParam.NamespaceName = rpAndNamespaceName[1]
	}

	packagePath := filepath.Join(ctx.SDKPath, packageRelativePath)
	modulePath := filepath.Join(ctx.SDKPath, moduleRelativePath)

	version, err := semver.NewVersion("0.1.0")
	if err != nil {
		return nil, err
	}

	// check if the package is onboard or update, to init different generator
	var generator Generator
	commonGenerator := &TypeSpecCommonGenerator{
		GenerateContext:     ctx,
		PackagePath:         packagePath,
		ModulePath:          modulePath,
		PackageRelativePath: packageRelativePath,
		ModuleRelativePath:  moduleRelativePath,
		Version:             version,
		IsSubPackage:        isSubPackage,
	}

	status, err := changelog.DetermineModuleStatus(modulePath, *ctx.SDKRepo)
	if err != nil {
		return nil, err
	}
	switch status {
	case utils.PackageStatusNew:
		generator = &TypeSpecOnBoardGenerator{TypeSpecCommonGenerator: commonGenerator}
	default:
		generator = &TypeSpecUpdateGenerator{TypeSpecCommonGenerator: commonGenerator}
	}

	if err = generator.PreGenerate(generateParam); err != nil {
		return nil, err
	}

	if err = generator.Generate(generateParam); err != nil {
		return nil, err
	}

	if err = generator.GenChangeLog(generateParam); err != nil {
		return nil, err
	}

	return generator.AfterGenerate(generateParam)
}

func (t *TypeSpecCommonGenerator) PreGenerate(generateParam *GenerateParam) error {
	return nil
}

func (t *TypeSpecCommonGenerator) GenChangeLog(generateParam *GenerateParam) error {
	log.Printf("Start to generate changelog for package...")
	var err error
	t.ChangelogResult, err = changelog.GenerateChangelog(t.ModulePath, *t.SDKRepo, t.IsCurrentPreview)
	if err != nil {
		return err
	}
	return nil
}

func (t *TypeSpecCommonGenerator) Generate(generateParam *GenerateParam) error {
	version := t.Version
	ctx := t.GenerateContext
	log.Printf("Start to run `tsp-client init` to generate the code...")
	defaultModuleVersion := version.String()
	emitOption := ""
	if !t.IsSubPackage {
		emitOption = fmt.Sprintf("module-version=%s", defaultModuleVersion)
	}
	if generateParam.TypeSpecEmitOption != "" {
		emitOption = fmt.Sprintf("%s;%s", emitOption, generateParam.TypeSpecEmitOption)
	}
	if generateParam.ApiVersion != "" {
		emitOption = fmt.Sprintf("%s;api-version=%s", emitOption, generateParam.ApiVersion)
	}
	err := ExecuteTypeSpecGenerate(ctx, emitOption, generateParam.TspClientOptions)
	if err != nil {
		return err
	}
	return nil
}

func (t *TypeSpecCommonGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	packagePath := t.PackagePath
	modulePath := t.ModulePath
	if t.IsSubPackage {
		// remove go.mod for sub package
		goModPath := filepath.Join(packagePath, utils.GoModFileName)
		if _, err := os.Stat(goModPath); !os.IsNotExist(err) {
			if err = os.Remove(goModPath); err != nil {
				return nil, err
			}
		}
	}

	if !generateParam.SkipUpdateDep {
		log.Printf("##[command]Executing go get -u github.com/Azure/azure-sdk-for-go/sdk/azcore toolchain@none go@1.24.0 in %s\n", modulePath)
		if err := ExecuteGo(modulePath, "get", "-u", "github.com/Azure/azure-sdk-for-go/sdk/azcore", "toolchain@none", "go@1.24.0"); err != nil {
			return nil, err
		}
		log.Printf("##[command]Executing go get -u github.com/Azure/azure-sdk-for-go/sdk/azidentity toolchain@none go@1.24.0 in %s\n", modulePath)
		if err := ExecuteGo(modulePath, "get", "-u", "github.com/Azure/azure-sdk-for-go/sdk/azidentity", "toolchain@none", "go@1.24.0"); err != nil {
			return nil, err
		}
	}

	log.Printf("##[command]Executing go mod tidy in %s\n", modulePath)
	if err := ExecuteGo(modulePath, "mod", "tidy"); err != nil {
		return nil, err
	}

	log.Printf("Update README.md ClientFactory...")
	err := UpdateReadmeClientFactory(t.PackagePath)
	if err != nil {
		// only log error, avoid breaking the process
		log.Printf("Update README.md ClientFactory failed! err: %v", err)
	}
	return nil, nil
}

func (t *TypeSpecOnBoardGenerator) PreGenerate(generateParam *GenerateParam) error {
	version := t.Version
	modulePath := t.ModulePath
	moduleRelativePath := t.ModuleRelativePath

	log.Printf("Module '%s' changelog not exist, do onboard process", modulePath)
	if generateParam.SpecificPackageTitle == "" {
		generateParam.SpecificPackageTitle = cases.Title(language.English).String(generateParam.RPName)
	}

	// onboard is always preview
	t.IsCurrentPreview = true

	log.Printf("Start to use template to generate new rp folder and basic package files...")
	sdkBasicInfo := map[string]any{
		"rpName":             generateParam.RPName,
		"packageName":        generateParam.NamespaceName,
		"moduleRelativePath": moduleRelativePath,
		"serviceDir":         strings.Replace(moduleRelativePath, "sdk/", "", 1),
		"packageTitle":       generateParam.SpecificPackageTitle,
		"packageVersion":     version.String(),
		"releaseDate":        generateParam.ReleaseDate,
	}
	return typespec.ParseTypeSpecTemplates(filepath.Join(t.SDKPath, "eng/tools/generator/template/typespec"), modulePath, sdkBasicInfo, nil)
}

func (t *TypeSpecOnBoardGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	var err error
	version := t.Version
	modulePath := t.ModulePath
	packagePath := t.PackagePath
	packageRelativePath := t.PackageRelativePath

	var prl utils.PullRequestLabel
	log.Printf("Replace {{NewClientName}} placeholder in the README.md ")
	if err = ReplaceNewClientNamePlaceholder(modulePath, *t.ChangelogResult.NewExports); err != nil {
		return nil, err
	}

	if !generateParam.SkipGenerateExample {
		log.Printf("Generate examples...")
	}

	if err = changelog.CreateNewChangelog(t.PackagePath, *t.SDKRepo, "0.1.0", generateParam.ReleaseDate); err != nil {
		return nil, err
	}

	// issue: https://github.com/Azure/azure-sdk-for-go/issues/23877
	prl = utils.FirstBetaLabel

	if _, err := t.TypeSpecCommonGenerator.AfterGenerate(generateParam); err != nil {
		return nil, err
	}

	return &GenerateResult{
		Version:             version.String(),
		RPName:              generateParam.RPName,
		PackageName:         generateParam.NamespaceName,
		PackageAbsPath:      packagePath,
		Changelog:           *t.ChangelogResult.ChangelogData,
		ChangelogMD:         t.ChangelogResult.ChangelogData.ToCompactMarkdown() + "\n" + t.ChangelogResult.ChangelogData.GetChangeSummary(),
		PullRequestLabels:   string(prl),
		PackageRelativePath: packageRelativePath,
		ModuleRelativePath:  t.ModuleRelativePath,
		GenerationType:      "TypeSpecOnBoard",
	}, nil
}

func (t *TypeSpecUpdateGenerator) PreGenerate(generateParam *GenerateParam) error {
	log.Printf("Package '%s' existed, do update process", t.PackagePath)

	var override *bool
	switch generateParam.SdkReleaseType {
	case utils.SDKReleaseTypeBeta:
		override = new(bool)
		*override = true
	case utils.SDKReleaseTypeStable:
		override = new(bool)
		*override = false
	}

	var err error
	t.IsCurrentPreview, err = version.IsCurrentPreviewVersion(t.PackagePath, *t.SDKRepo, override)
	if err != nil {
		return err
	}

	return nil
}

func (t *TypeSpecUpdateGenerator) AfterGenerate(generateParam *GenerateParam) (*GenerateResult, error) {
	var prl utils.PullRequestLabel
	var err error
	var newVersion *semver.Version
	packagePath := t.PackagePath
	modulePath := t.ModulePath
	packageRelativePath := t.PackageRelativePath
	moduleRelativePath := t.ModuleRelativePath
	isCurrentPreview := t.IsCurrentPreview

	log.Printf("Calculate new version...")
	newVersion, prl, err = version.CalculateNewVersion(t.ChangelogResult.ChangelogData, t.ChangelogResult.PreviousVersion, isCurrentPreview)
	if err != nil {
		return nil, err
	}

	log.Printf("Add changelog to file...")
	changelogMd, err := changelog.AddChangelogToFileWithReplacement(t.ChangelogResult.ChangelogData, newVersion, modulePath, generateParam.ReleaseDate)
	if err != nil {
		return nil, err
	}

	log.Printf("Update all version files...")
	if err = version.UpdateAllVersionFiles(packagePath, newVersion, *t.SDKRepo); err != nil {
		return nil, err
	}

	log.Printf("Replace README.md NewClient name...")
	if err = ReplaceReadmeNewClientName(modulePath, *t.ChangelogResult.NewExports); err != nil {
		return nil, err
	}

	// Example generation should be the last step because the package import relay on the new calculated version
	if !generateParam.SkipGenerateExample {
		log.Printf("Generate examples...")
	}

	generationType := "TypeSpecUpdate"

	// remove autorest.md and build.go
	autorestMdPath := filepath.Join(packagePath, "autorest.md")
	if _, err := os.Stat(autorestMdPath); !os.IsNotExist(err) {
		log.Println("Remove autorest.md...")
		if err = os.Remove(autorestMdPath); err != nil {
			return nil, err
		}
		generationType = "MigrateToTypeSpec"
	}
	buildGoPath := filepath.Join(packagePath, "build.go")
	if _, err := os.Stat(buildGoPath); !os.IsNotExist(err) {
		log.Println("Remove build.go...")
		if err = os.Remove(buildGoPath); err != nil {
			return nil, err
		}
	}

	if _, err := t.TypeSpecCommonGenerator.AfterGenerate(generateParam); err != nil {
		return nil, err
	}

	return &GenerateResult{
		Version:             newVersion.String(),
		RPName:              generateParam.RPName,
		PackageName:         generateParam.NamespaceName,
		PackageAbsPath:      packagePath,
		Changelog:           *t.ChangelogResult.ChangelogData,
		ChangelogMD:         changelogMd + "\n" + t.ChangelogResult.ChangelogData.GetChangeSummary(),
		PullRequestLabels:   string(prl),
		PackageRelativePath: packageRelativePath,
		ModuleRelativePath:  moduleRelativePath,
		GenerationType:      generationType,
	}, nil
}
